﻿using System;
using System.IO;
using System.Threading;
using System.Xml;
using Sasa.QualityTools.CodeAnalysis.Common;
using Sasa.QualityTools.CodeAnalysis.Core.Properties;
using System.Collections.Generic;

namespace Sasa.QualityTools.CodeAnalysis.Core.Models
{
    public class CodeAnalysisConfiguration
    {
        private CodeAnalysisConfiguration parent;

        private bool enableSearchGac;

        private bool ignoreGeneratedCode;

        private double timeout;

        private string ruleSet;

        private ICollection<string> filterPatterns;


        public string ConfigurationPath { get; private set; }

        public bool EnableCodeAnalysis { get; set; }

        public bool OverrideEnableSearchGac { get; set; }

        public bool OverrideIgnoreGeneratedCode { get; set; }

        public bool OverrideTimeout { get; set; }

        public bool OverrideRuleSet { get; set; }

        public OverrideMode OverrideFilterPatterns { get; set; }

        public bool EnableSearchGac
        {
            get
            {
                return (parent != null && !OverrideEnableSearchGac) ? parent.EnableSearchGac : enableSearchGac;
            }
            set
            {
                enableSearchGac = value;
            }
        }

        public bool IgnoreGeneratedCode
        {
            get
            {
                return (parent != null && !OverrideIgnoreGeneratedCode) ? parent.IgnoreGeneratedCode : ignoreGeneratedCode;
            }
            set
            {
                ignoreGeneratedCode = value;
            }
        }

        public double Timeout
        {
            get
            {
                return (parent != null && !OverrideTimeout) ? parent.Timeout : timeout;
            }
            set
            {
                timeout = value;
            }
        }

        public string RuleSet
        {
            get
            {
                return (parent != null && !OverrideRuleSet) ? parent.RuleSet : ruleSet;
            }
            set
            {
                ruleSet = value;
            }
        }

        public ICollection<string> FilterPatterns
        {
            get
            {
                if (parent == null)
                {
                    return filterPatterns;
                }
                else
                {
                    switch (OverrideFilterPatterns)
                    {
                        case OverrideMode.Replace:
                            return filterPatterns;
                        case OverrideMode.Merge:
                            var result = new List<string>();
                            result.AddRange(filterPatterns);
                            result.AddRange(parent.filterPatterns);
                            return result;
                        default:
                            return parent.filterPatterns;
                    }
                }
            }
        }


        private CodeAnalysisConfiguration(string configurationPath)
        {
            ConfigurationPath = Path.GetFullPath(configurationPath);
            EnableCodeAnalysis = true;
            enableSearchGac = true;
            ignoreGeneratedCode = true;
            timeout = 60;
            filterPatterns = new List<string>();
            filterPatterns.Add(@"^.+\.[Dd]esigner\.[^\.]+$");
            filterPatterns.Add(@"^.+\.[Gg]enerated\.[^\.]+$");
            filterPatterns.Add(@"^.+\.g\.[^\.]+$");
            filterPatterns.Add(@"^.+\.g\.i\.[^\.]+$");
        }


        public static CodeAnalysisConfiguration Load(string configurationPath)
        {
            Logger.Info("Loading code analysis configuration from '{0}'.", configurationPath);

            if (String.IsNullOrWhiteSpace(configurationPath))
            {
                throw new ArgumentException(Text.Format(Resources.ConfigPathIsInvalid, configurationPath), "configurationPath");
            }

            var config = new CodeAnalysisConfiguration(configurationPath);
            if (!File.Exists(configurationPath))
            {
                return config;
            }

            try
            {
                using (var stream = new FileStream(configurationPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    Load(stream, config);
                }
                return config;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to load the code analysis configuration from '{0}', so FxCop Integrator generated the default configuration instead.", configurationPath);
                return new CodeAnalysisConfiguration(configurationPath);
            }
        }

        internal static void Load(Stream stream, CodeAnalysisConfiguration configuration)
        {
            var xml = new XmlDocument();
            xml.Load(stream);

            configuration.EnableCodeAnalysis
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/@Enable");

            configuration.OverrideEnableSearchGac
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/EnableSearchGAC/@Override");
            configuration.EnableSearchGac
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/EnableSearchGAC/@Value");

            configuration.OverrideIgnoreGeneratedCode
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/IgnoreGeneratedCode/@Override");
            configuration.IgnoreGeneratedCode
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/IgnoreGeneratedCode/@Value");

            configuration.OverrideTimeout
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/Timeout/@Override");
            configuration.Timeout
                = FetchValue<double>(xml, "/CodeAnalysisConfiguration/Timeout/@Value");

            var converter = new RuleSetConverter(Path.GetDirectoryName(configuration.ConfigurationPath));
            configuration.OverrideRuleSet
                = FetchValue<bool>(xml, "/CodeAnalysisConfiguration/RuleSet/@Override");
            configuration.RuleSet
                = converter.Reverse(FetchValue<string>(xml, "/CodeAnalysisConfiguration/RuleSet/@Value"));

            OverrideMode overrideFilterPatterns;
            var success = Enum.TryParse( FetchValue<string>(xml, "/CodeAnalysisConfiguration/FilterPatterns/@Override"), true, out overrideFilterPatterns);
            configuration.OverrideFilterPatterns = (success) ? overrideFilterPatterns : OverrideMode.Inherit;
            configuration.FilterPatterns.Clear();
            foreach (var pattern in FetchValues<string>(xml, "/CodeAnalysisConfiguration/FilterPatterns/FilterPattern/@Value"))
            {
                configuration.FilterPatterns.Add(pattern);
            }
        }

        private static T FetchValue<T>(XmlDocument xml, string xpath)
        {
            var culture = Thread.CurrentThread.CurrentUICulture;
            var node = xml.SelectSingleNode(xpath);
            return (node != null) ? (T)Convert.ChangeType(node.Value, typeof(T), culture) : default(T);
        }

        private static IEnumerable<T> FetchValues<T>(XmlDocument xml, string xpath)
        {
            var result = new List<T>();

            var culture = Thread.CurrentThread.CurrentUICulture;
            var nodes = xml.SelectNodes(xpath);
            if (nodes != null)
            {
                foreach (XmlNode node in nodes)
                {
                    result.Add((T)Convert.ChangeType(node.Value, typeof(T), culture));
                }
            }

            return result;
        }

        public void Override(CodeAnalysisConfiguration parent)
        {
            this.parent = parent;
        }

        public void Save()
        {
            Logger.Info("Saving code analysis configuration to '{0}'.", ConfigurationPath);

            Directory.CreateDirectory(Path.GetDirectoryName(ConfigurationPath));
            using (var stream = new FileStream(ConfigurationPath, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                Save(stream);
            }
        }

        internal void Save(Stream stream)
        {
            var settings = new XmlWriterSettings();
            settings.Indent = true;
            using (var writer = XmlWriter.Create(stream, settings))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("CodeAnalysisConfiguration");
                WriteRoot(writer);
                WriteValue(writer, "EnableSearchGAC", OverrideEnableSearchGac, enableSearchGac);
                WriteValue(writer, "IgnoreGeneratedCode", OverrideIgnoreGeneratedCode, ignoreGeneratedCode);
                WriteValue(writer, "Timeout", OverrideTimeout, timeout);
                WriteRuleSetValue(writer);
                WriteFilterPatterns(writer);
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }
        }

        private void WriteRoot(XmlWriter writer)
        {
            writer.WriteStartAttribute("Version");
            writer.WriteValue("2.0");
            writer.WriteEndAttribute();

            writer.WriteStartAttribute("Enable");
            writer.WriteValue(EnableCodeAnalysis);
            writer.WriteEndAttribute();
        }

        private static void WriteValue(XmlWriter writer, string elementName, bool isOverride, object value)
        {
            writer.WriteStartElement(elementName);
            writer.WriteStartAttribute("Override");
            writer.WriteValue(isOverride);
            writer.WriteEndAttribute();
            writer.WriteStartAttribute("Value");
            writer.WriteValue(value ?? "");
            writer.WriteEndAttribute();
            writer.WriteEndElement();
        }

        private void WriteRuleSetValue(XmlWriter writer)
        {
            writer.WriteStartElement("RuleSet");
            writer.WriteStartAttribute("Override");
            writer.WriteValue(OverrideRuleSet);
            writer.WriteEndAttribute();

            if (ruleSet != null)
            {
                var converter = new RuleSetConverter(Path.GetDirectoryName(ConfigurationPath));
                writer.WriteStartAttribute("Value");
                writer.WriteValue(converter.Convert(ruleSet.ToString()) ?? "");
                writer.WriteEndAttribute();
            }

            writer.WriteEndElement();
        }

        private void WriteFilterPatterns(XmlWriter writer)
        {
            writer.WriteStartElement("FilterPatterns");
            
            writer.WriteStartAttribute("Override");
            writer.WriteValue(OverrideFilterPatterns.ToString().ToLowerInvariant());
            writer.WriteEndAttribute();

            foreach (var pattern in filterPatterns)
            {
                writer.WriteStartElement("FilterPattern");
                writer.WriteStartAttribute("Value");
                writer.WriteValue(pattern);
                writer.WriteEndAttribute();
                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }
    }
}
